#!/usr/bin/perl
use Test::More;

# Load common subroutines.
use File::Basename qw(dirname basename);
use Cwd qw(abs_path);

BEGIN {
    $basedir = dirname($0);
    $basedir = `cd $basedir && pwd`;
    chomp($basedir);
}
use lib $basedir;
use Filesystem
  qw(check_config udisks2_stop udisks2_restart get_loop_dev attach_dev make_fs mk_mntpoint_1 mk_mntpoint_2 cleanup cleanup1 reaper nfs_gen_opts);

BEGIN {
    # extract fs_type
    $test_name = basename($basedir);
    $fs_type   = $test_name ne "filesystem" ? $test_name : " ";

    # Options: -v Verbose, -e enable udisks(8) daemon, -f filesystem type
    $v              = " ";
    $disable_udisks = 1;
    $udisks2_status = 0;
    $quota_checks   = 1;
    $nfs_enabled    = 0;
    $vfat_enabled   = 0;

    $i = 0;
    foreach $arg (@ARGV) {
        if ( $arg eq "-v" ) {
            $v = $arg;
        }
        elsif ( $arg eq "-e" ) {
            $disable_udisks = 0;
        }
        elsif ( $arg eq "-f" ) {
            $fs_type = $ARGV[ $i + 1 ];
        }
        $i++;
    }

    # If NFS specified inform how to run
    if ( $fs_type eq "nfs" or $fs_type eq "nfs4" ) {
        plan skip_all => "To run NFS use 'tools/nfs.sh filesystem [-v]'";
    }

    # Get filesystem type if not specified
    if ( $fs_type eq " " ) {
        $fs_type = `findmnt -n -o FSTYPE -T $basedir`;
        chomp $fs_type;
    }

    # Obtain an appropriate set of mount options for NFS.
    # rootcontext is not supported.
    # $seclabel_type: No seclabel = 0, fscontext = 1, context = 2
    if ( $fs_type eq "nfs4" or $fs_type eq "nfs" ) {
        ( $dev, $nfs_mount_opts, $nfs_inval_mount_opts, $seclabel_type ) =
          nfs_gen_opts( $fs_type, $basedir, "filesystem" );
        $nfs_enabled = 1;
    }
    elsif ( $fs_type eq "vfat" ) {
        $vfat_enabled = 1;
    }

    # XFS supports quotas internally and therefore does not require calling
    # security_quota_on().
    if ( $fs_type eq "xfs" ) {
        $test_count   = 62;
        $quota_checks = 1;
    }
    elsif ( $fs_type eq "nfs4" or $fs_type eq "nfs" ) {
        $test_count   = 55;
        $quota_checks = 0;
    }
    elsif ( $fs_type eq "vfat" ) {
        $test_count   = 55;
        $quota_checks = 0;
    }
    else {
        $test_count = 69;
    }

    if ($nfs_enabled) {

        # hooks.c may_create() FILESYSTEM__ASSOCIATE is tested via the
        # may_create_no_associate() test in nfs_filesystem/test
        $test_count -= 3;

        # hooks.c selinux_inode_setxattr() FILESYSTEM__ASSOCIATE is tested
        # via the inode_setxattr_no_associate() test in nfs_filesystem/test
        $test_count -= 3;

        # Remove additional Test Invalid Mount tests
        $test_count -= 1;

        # Remove tests involving multiple *context= options as invalid
        $test_count -= 20;

        # Removed when no context option is set in a test
        if ( $seclabel_type eq 0 ) {
            $test_count -= 4;
        }

        # Removed if fscontext option set in a test
        elsif ( $seclabel_type eq 1 ) {
            $test_count -= 1;
        }
    }
    elsif ($vfat_enabled) {

        # For setfilecon tests as not supported
        $test_count -= 2;

        # For hooks.c may_create() FILESYSTEM__ASSOCIATE as not supported
        $test_count -= 3;

        # For hooks.c selinux_inode_setxattr() FILESYSTEM__ASSOCIATE as
        # not supported
        $test_count -= 3;

        # For tests with defcontext= options as not supported
        $test_count -= 6;
    }

    # Check if watch and/or named type_transition rules configured
    ( $addit, $test_watch, $test_name_trans, $test_type_trans ) =
      check_config( $basedir, "$basedir/fanotify_fs", $nfs_enabled,
        $vfat_enabled );

    $test_count += $addit;
    plan tests => $test_count;
}

print "Testing filesystem fs_type: $fs_type\n";

# mount(2) MS_BIND | MS_PRIVATE requires an absolute path to a private mount
# point before MS_MOVE
$private_path = abs_path($basedir) . "/mntpoint";

# Keep a list of devices used for removal at end of test.
$device_count = 0;
my @device_list;

if ($disable_udisks) {
    $udisks2_status = udisks2_stop();
}

cleanup($basedir);

############### Test setfscreatecon(3) ##########################
system("mkdir -p $basedir/mntpoint 2>/dev/null");

print "Test setfscreatecon(3)\n";
$result = system(
"runcon -t test_setfscreatecon_t $basedir/fs_relabel $v -b $basedir/mntpoint -t test_setfscreatecon_newcon_t"
);
ok( $result eq 0 );

$result = system(
"runcon -t test_no_setfscreatecon_t $basedir/fs_relabel $v -b $basedir/mntpoint -t test_setfscreatecon_newcon_t 2>&1"
);
ok( $result >> 8 eq 13 );

system("rm -rf $basedir/mntpoint 2>/dev/null");

############### Test Basic Mount/Unmount ##########################
mk_mntpoint_1($private_path);

if ($nfs_enabled) {
    $mount_opts = $nfs_mount_opts;
}
else {
    ( $dev, $device_count ) = get_loop_dev( \@device_list, $device_count );
    make_fs( $fs_type, $dev, $basedir );

    # For XFS quota tests.
    if ( $fs_type eq "xfs" ) {
        $mount_opts =
"uquota,prjquota,rootcontext=system_u:object_r:test_filesystem_file_t:s0";
    }
    elsif ($quota_checks) {
        $mount_opts =
"quota,usrquota,grpquota,rootcontext=system_u:object_r:test_filesystem_file_t:s0";
    }
    else {
        $mount_opts = "rootcontext=system_u:object_r:test_filesystem_file_t:s0";
    }
}

print "Mount $fs_type filesystem on $basedir/mntpoint/mp1\n";
print "Using mount options:\n\t$mount_opts\n";
$result = system(
"runcon -t test_filesystem_t $basedir/mount -s $dev -t $private_path/mp1 -f $fs_type -o $mount_opts $v"
);
ok( $result eq 0 );

print "Then remount\n";
$result = system(
"runcon -t test_filesystem_t $basedir/mount -r -s $dev -t $private_path/mp1 -f $fs_type -o $mount_opts $v"
);
ok( $result eq 0 );

if ($quota_checks) {
    if ( $fs_type eq "xfs" ) {
        print "# XFS quota test with mount options: uquota,prjquota\n";
        $result = system(
            "runcon -t test_filesystem_t $basedir/xfs_quotas_test $v -s $dev");
        ok( $result eq 0 );
    }
    else {
        print "Running quotacheck(8) to init user/group quota files\n";

      # On RHEL-6, there is a type transition to quota_t when running quotacheck
      # as unconfined_t. Using "runcon `id -Z` quotacheck ..." resolves this.
        $result =
          system("runcon `id -Z` quotacheck -ugF vfsv0 $private_path/mp1");
        ok( $result eq 0 );

        print "Toggle User & Group quotas on/off\n";
        $result = system(
"runcon -t test_filesystem_t $basedir/quotas_test -s $dev -t $private_path/mp1/aquota.user $v"
        );
        ok( $result eq 0 );
        $result = system(
"runcon -t test_filesystem_t $basedir/quotas_test -s $dev -t $private_path/mp1/aquota.group $v"
        );
        ok( $result eq 0 );
    }
}
print "Get statfs(2)\n";
$result =
  system(
    "runcon -t test_filesystem_t $basedir/statfs_test -t $basedir/mntpoint $v");
ok( $result eq 0 );

if ($test_type_trans) {
    print
"Creating 'trans_test_file' and checking context changed via type_transition rule\n";
    $result = system(
"runcon -t test_filesystem_t $basedir/create_file -f $private_path/mp1/trans_test_file -e test_filesystem_filetranscon_t $v"
    );
    ok( $result eq 0 );
}

if ( not $vfat_enabled ) {
    print "Creating 'test_file' and changing its context via setfilecon(3)\n";
    $result =
      system(
"runcon -t test_filesystem_t $basedir/create_file_change_context -t test_filesystem_filecon_t -f $private_path/mp1/test_file $v"
      );
    ok( $result eq 0 );
}

if ($test_name_trans) {
    print
"Creating 'name_trans_test_file1' and checking context changed via name-based type_transition rule\n";
    $result = system(
"runcon -t test_filesystem_t $basedir/create_file -f $private_path/mp1/name_trans_test_file1 -e test_filesystem_filenametranscon1_t $v"
    );
    ok( $result eq 0 );

    print
"Creating 'name_trans_test_file2' and checking context changed via name-based type_transition rule\n";
    $result = system(
"runcon -t test_filesystem_t $basedir/create_file -f $basedir/mntpoint/mp1/name_trans_test_file2 -e test_filesystem_filenametranscon2_t $v"
    );
    ok( $result eq 0 );
}

if ($test_watch) {
    print "fanotify(7) test - FAN_MARK_FILESYSTEM\n";
    $result = system(
"runcon -t test_filesystem_t $basedir/fanotify_fs $v -t $basedir/mntpoint/mp1"
    );
    ok( $result eq 0 );

    print "fanotify(7) test - FAN_MARK_MOUNT\n";
    $result = system(
"runcon -t test_filesystem_t $basedir/fanotify_fs $v -m -t $basedir/mntpoint/mp1"
    );
    ok( $result eq 0 );
}

print "Unmount filesystem from $basedir/mntpoint/mp1\n";
$result =
  system("runcon -t test_filesystem_t $basedir/umount -t $private_path/mp1 $v");
ok( $result eq 0 );

print "Removing: $dev $basedir/mntpoint $basedir/fstest\n";
cleanup1( $basedir, $dev );

############### Test Move Mount ##########################
mk_mntpoint_1($private_path);

if ($nfs_enabled) {
    $mount_opts = $nfs_mount_opts;
}
else {
    ( $dev, $device_count ) = get_loop_dev( \@device_list, $device_count );
    make_fs( $fs_type, $dev, $basedir );
    $mount_opts = "rootcontext=system_u:object_r:test_filesystem_file_t:s0";
}

print "Set mount MS_BIND on filesystem\n";
$result = system(
"runcon -t test_filesystem_t $basedir/mount -s $private_path -t $private_path -b $v"
);
ok( $result eq 0 );

print "Set mount MS_PRIVATE on filesystem\n";
$result =
  system("runcon -t test_filesystem_t $basedir/mount -t $private_path -p $v");
ok( $result eq 0 );

mk_mntpoint_2($private_path);

print "Mount $fs_type filesystem on $basedir/mntpoint/mp1\n";
print "Using mount options:\n\t$mount_opts\n";
$result = system(
"runcon -t test_filesystem_t $basedir/mount -s $dev -t $private_path/mp1 -f $fs_type -o $mount_opts $v"
);
ok( $result eq 0 );

print "Set mount MS_MOVE on filesystem\n";
$result = system(
"runcon -t test_filesystem_t $basedir/mount -s $private_path/mp1 -t $private_path/mp2 -m  $v"
);
ok( $result eq 0 );

print "Unmount filesystem from $basedir/mntpoint/mp2\n";
$result =
  system("runcon -t test_filesystem_t $basedir/umount -t $private_path/mp2 $v");
ok( $result eq 0 );

print "Unmount filesystem from $basedir/mntpoint\n";
$result =
  system("runcon -t test_filesystem_t $basedir/umount -t $private_path $v");
ok( $result eq 0 );

print "Removing: $dev $basedir/mntpoint $basedir/fstest\n";
cleanup1( $basedir, $dev );

############### Deny filesystem { relabelfrom } ##########################
# hooks.c may_context_mount_sb_relabel() FILESYSTEM__RELABELFROM
#
# Also tested in nfs_filesystem/test
#
if ( ( $nfs_enabled and $seclabel_type ne 0 ) or not $nfs_enabled ) {
    mk_mntpoint_1($private_path);

    if ($nfs_enabled) {
        $mount_opts = $nfs_mount_opts;
    }
    else {
        ( $dev, $device_count ) = get_loop_dev( \@device_list, $device_count );
        make_fs( $fs_type, $dev, $basedir );
        $mount_opts =
"rootcontext=system_u:object_r:test_filesystem_sb_relabel_no_relabelfrom_t:s0";
    }

    print "Mount $fs_type filesystem on $basedir/mntpoint/mp1\n";
    print "Using mount options:\n\t$mount_opts\n";
    $result = system(
"runcon -t test_filesystem_sb_relabel_no_relabelfrom_t $basedir/mount -s $dev -t $basedir/mntpoint/mp1 -f $fs_type -o $mount_opts $v 2>&1"
    );
    ok( $result >> 8 eq 13 );

    print "Removing: $dev $basedir/mntpoint $basedir/fstest\n";
    cleanup1( $basedir, $dev );
}

############### Deny filesystem { relabelto } ##########################
# hooks.c may_context_mount_sb_relabel() FILESYSTEM__RELABELTO
#
# Also tested in nfs_filesystem/test
#
if ( ( $nfs_enabled and $seclabel_type eq 1 ) or not $nfs_enabled ) {
    mk_mntpoint_1($private_path);

    if ($nfs_enabled) {
        $mount_opts = $nfs_mount_opts;
    }
    else {
        ( $dev, $device_count ) = get_loop_dev( \@device_list, $device_count );
        make_fs( $fs_type, $dev, $basedir );
        $mount_opts =
"fscontext=system_u:object_r:test_filesystem_sb_relabel_no_relabelto_t:s0";
    }

    print "Mount $fs_type filesystem on $basedir/mntpoint/mp1\n";
    print "Using mount options:\n\t$mount_opts\n";
    $result = system(
"runcon -t test_filesystem_sb_relabel_no_relabelto_t $basedir/mount -s $dev -t $basedir/mntpoint/mp1 -f $fs_type -o $mount_opts $v 2>&1"
    );
    ok( $result >> 8 eq 13 );

    print "Removing: $dev $basedir/mntpoint $basedir/fstest\n";
    cleanup1( $basedir, $dev );
}

############### Deny filesystem { relabelfrom } ##########################
# hooks.c may_context_mount_inode_relabel() FILESYSTEM__RELABELFROM
#
# Also tested in nfs_filesystem/test
#
if ( ( $nfs_enabled and $seclabel_type ne 0 ) or not $nfs_enabled ) {
    mk_mntpoint_1($private_path);

    if ($nfs_enabled) {
        $mount_opts = $nfs_mount_opts;
    }
    else {
        ( $dev, $device_count ) = get_loop_dev( \@device_list, $device_count );
        make_fs( $fs_type, $dev, $basedir );
        $mount_opts =
"rootcontext=system_u:object_r:test_filesystem_no_inode_no_relabelfrom_t:s0";
    }

    print "Mount $fs_type filesystem on $basedir/mntpoint/mp1\n";
    print "Using mount options:\n\t$mount_opts\n";
    $result = system(
"runcon -t test_filesystem_no_inode_no_relabelfrom_t $basedir/mount -s $dev -t $basedir/mntpoint/mp1 -f $fs_type -o $mount_opts $v 2>&1"
    );
    ok( $result >> 8 eq 13 );

    print "Removing: $dev $basedir/mntpoint $basedir/fstest\n";
    cleanup1( $basedir, $dev );
}
############### Deny filesystem { associate } ##########################
# hooks.c may_context_mount_inode_relabel() FILESYSTEM__ASSOCIATE
#
# Tested in nfs_filesystem/test
#
if ( not $nfs_enabled ) {
    mk_mntpoint_1($private_path);

    ( $dev, $device_count ) = get_loop_dev( \@device_list, $device_count );
    make_fs( $fs_type, $dev, $basedir );
    $mount_opts =
"rootcontext=system_u:object_r:test_filesystem_inode_relabel_no_associate_t:s0";

    print "Mount $fs_type filesystem on $basedir/mntpoint/mp1\n";
    print "Using mount options:\n\t$mount_opts\n";
    $result = system(
"runcon -t test_filesystem_inode_relabel_no_associate_t $basedir/mount -s $dev -t $basedir/mntpoint/mp1 -f $fs_type -o $mount_opts $v 2>&1"
    );
    ok( $result >> 8 eq 13 );

    print "Removing: $dev $basedir/mntpoint $basedir/fstest\n";
    cleanup1( $basedir, $dev );
}

############### Deny filesystem { associate } ##########################
# hooks.c may_create() FILESYSTEM__ASSOCIATE
#
# Tested in nfs_filesystem/test
#
if ( not $nfs_enabled and not $vfat_enabled ) {
    mk_mntpoint_1($private_path);
    ( $dev, $device_count ) = get_loop_dev( \@device_list, $device_count );
    make_fs( $fs_type, $dev, $basedir );

# Use this fscontext= to get sensible audit log entry of:
#  "allow unlabeled_t test_filesystem_may_create_no_associate_t:filesystem associate;"
    $mount_opts =
"fscontext=system_u:object_r:test_filesystem_may_create_no_associate_t:s0";

    print "Mount $fs_type filesystem on $basedir/mntpoint/mp1\n";
    print "Using mount options:\n\t$mount_opts\n";
    $result = system(
"runcon -t test_filesystem_may_create_no_associate_t $basedir/mount -s $dev -t $basedir/mntpoint/mp1 -f $fs_type -o $mount_opts $v"
    );
    ok( $result eq 0 );

    print "Creating test file $basedir/mntpoint/mp1/test_file\n";
    $result = system(
"runcon -t test_filesystem_may_create_no_associate_t $basedir/create_file_change_context -t unconfined_t -f $basedir/mntpoint/mp1/test_file $v 2>&1"
    );
    ok( $result >> 8 eq 13 );    # EACCES

    print "Unmount filesystem from $basedir/mntpoint/mp1\n";
    $result = system(
"runcon -t test_filesystem_may_create_no_associate_t $basedir/umount -t $basedir/mntpoint/mp1 $v"
    );
    ok( $result eq 0 );

    print "Removing: $dev $basedir/mntpoint $basedir/fstest\n";
    cleanup1( $basedir, $dev );
}

if ($quota_checks) {
    ############### Deny filesystem { quotamod } ##########################
    # hooks.c selinux_quotactl() FILESYSTEM__QUOTAMOD
    #
    mk_mntpoint_1($private_path);
    ( $dev, $device_count ) = get_loop_dev( \@device_list, $device_count );
    make_fs( $fs_type, $dev, $basedir );

    if ( $fs_type eq "xfs" ) {
        $opts_no_quotamod =
"quota,uquota,prjquota,fscontext=system_u:object_r:test_filesystem_no_quotamod_t:s0";
    }
    else {
        $opts_no_quotamod =
"quota,usrquota,grpquota,fscontext=system_u:object_r:test_filesystem_no_quotamod_t:s0";
    }

    print "Mount $fs_type filesystem on $basedir/mntpoint/mp1\n";
    print "Using mount options:\n\t$opts_no_quotamod\n";
    $result = system(
"runcon -t test_filesystem_no_quotamod_t $basedir/mount -s $dev -t $basedir/mntpoint/mp1 -f $fs_type -o $opts_no_quotamod $v 2>&1"
    );
    ok( $result eq 0 );

    if ( $fs_type eq "xfs" ) {
        print "Toggle xfs quotas on/off\n";
        $result = system(
"runcon -t test_filesystem_no_quotamod_t $basedir/xfs_quotas_test -s $dev $v 2>&1"
        );
        ok( $result >> 8 eq 13 );
    }
    else {
      # No need to run quotacheck(8) as never gets far enough to read quota file
        print "Toggle User & Group quotas on/off\n";
        $result = system(
"runcon -t test_filesystem_no_quotamod_t $basedir/quotas_test -s $dev -t $private_path/mp1/aquota.user $v 2>&1"
        );
        ok( $result >> 8 eq 13 );
    }

    print "Unmount filesystem from $basedir/mntpoint/mp1\n";
    $result = system(
"runcon -t test_filesystem_no_quotamod_t $basedir/umount -t $basedir/mntpoint/mp1 $v"
    );
    ok( $result eq 0 );

    print "Removing: $dev $basedir/mntpoint $basedir/fstest\n";
    cleanup1( $basedir, $dev );

    ############### Deny filesystem { quotaget } ##########################
    # hooks.c selinux_quotactl() FILESYSTEM__QUOTAGET
    #
    mk_mntpoint_1($private_path);
    ( $dev, $device_count ) = get_loop_dev( \@device_list, $device_count );
    make_fs( $fs_type, $dev, $basedir );

    if ( $fs_type eq "xfs" ) {
        $opts_no_quotaget =
"quota,uquota,prjquota,context=system_u:object_r:test_filesystem_no_quotaget_t:s0";
    }
    else {
        $opts_no_quotaget =
"quota,usrquota,grpquota,context=system_u:object_r:test_filesystem_no_quotaget_t:s0";
    }

    print "Mount $fs_type filesystem on $basedir/mntpoint/mp1\n";
    print "Using mount options:\n\t$opts_no_quotaget\n";
    $result = system(
"runcon -t test_filesystem_no_quotaget_t $basedir/mount -s $dev -t $basedir/mntpoint/mp1 -f $fs_type -o $opts_no_quotaget $v"
    );
    ok( $result eq 0 );

    if ( $fs_type eq "xfs" ) {
        print "Toggle xfs quotas on/off\n";
        $result = system(
"runcon -t test_filesystem_no_quotaget_t $basedir/xfs_quotas_test -s $dev $v 2>&1"
        );
        ok( $result >> 8 eq 13 );
    }
    else {
        print "Running quotacheck(8) to init user/group quota files\n";
        $result =
          system("runcon `id -Z` quotacheck -ugF vfsv0 $private_path/mp1");
        ok( $result eq 0 );

        print "Toggle User & Group quotas on/off\n";
        $result = system(
"runcon -t test_filesystem_no_quotaget_t $basedir/quotas_test -s $dev -t $private_path/mp1/aquota.user $v 2>&1"
        );
        ok( $result >> 8 eq 13 );
    }

    print "Unmount filesystem from $basedir/mntpoint/mp1\n";
    $result = system(
"runcon -t test_filesystem_no_quotaget_t $basedir/umount -t $basedir/mntpoint/mp1 $v"
    );
    ok( $result eq 0 );

    print "Removing: $dev $basedir/mntpoint $basedir/fstest\n";
    cleanup1( $basedir, $dev );

    ############### Deny file { quotaon } ##########################
    # hooks.c selinux_quota_on() FILE__QUOTAON
    # XFS does not require this test
    #
    if ( not $fs_type eq "xfs" ) {
        mk_mntpoint_1($private_path);
        ( $dev, $device_count ) = get_loop_dev( \@device_list, $device_count );
        make_fs( $fs_type, $dev, $basedir );

        $opts_no_quotaon =
"quota,usrquota,grpquota,context=system_u:object_r:test_file_no_quotaon_t:s0";

        print "Mount $fs_type filesystem on $basedir/mntpoint/mp1\n";
        print "Using mount options:\n\t$opts_no_quotaon\n";
        $result = system(
"runcon -t test_file_no_quotaon_t $basedir/mount -s $dev -t $basedir/mntpoint/mp1 -f $fs_type -o $opts_no_quotaon $v"
        );
        ok( $result eq 0 );

        print "Running quotacheck(8) to init user/group quota files\n";
        $result =
          system("runcon `id -Z` quotacheck -ugF vfsv0 $private_path/mp1");
        ok( $result eq 0 );

        print "Toggle User quotas on/off\n";
        $result = system(
"runcon -t test_file_no_quotaon_t $basedir/quotas_test -s $dev -t $private_path/mp1/aquota.user $v 2>&1"
        );
        ok( $result >> 8 eq 13 );

        print "Unmount filesystem from $basedir/mntpoint/mp1\n";
        $result = system(
"runcon -t test_file_no_quotaon_t $basedir/umount -t $basedir/mntpoint/mp1 $v"
        );
        ok( $result eq 0 );

        print "Removing: $dev $basedir/mntpoint $basedir/fstest\n";
        cleanup1( $basedir, $dev );
    }
}    # End quota checks

############### Deny filesystem { mount } ##########################
# hooks.c selinux_sb_kern_mount() FILESYSTEM__MOUNT
mk_mntpoint_1($private_path);

if ($nfs_enabled) {
    $mount_opts = $nfs_mount_opts;
}
else {
    ( $dev, $device_count ) = get_loop_dev( \@device_list, $device_count );
    make_fs( $fs_type, $dev, $basedir );
    $mount_opts = "rootcontext=system_u:object_r:test_filesystem_no_mount_t:s0";
}

print "Mount $fs_type filesystem on $basedir/mntpoint/mp1\n";
print "Using mount options:\n\t$mount_opts\n";
$result = system(
"runcon -t test_filesystem_no_mount_t $basedir/mount -s $dev -t $basedir/mntpoint/mp1 -f $fs_type -o $mount_opts $v 2>&1"
);
ok( $result >> 8 eq 13 );

print "Removing: $dev $basedir/mntpoint $basedir/fstest\n";
cleanup1( $basedir, $dev );

############### Deny filesystem { getattr } ##########################
# hooks.c selinux_sb_statfs() FILESYSTEM__GETATTR
mk_mntpoint_1($private_path);

if ($nfs_enabled) {
    $mount_opts = $nfs_mount_opts;
}
else {
    ( $dev, $device_count ) = get_loop_dev( \@device_list, $device_count );
    make_fs( $fs_type, $dev, $basedir );
    $mount_opts =
      "rootcontext=system_u:object_r:test_filesystem_no_getattr_t:s0";
}

print "Mount $fs_type filesystem on $basedir/mntpoint/mp1\n";
print "Using mount options:\n\t$mount_opts\n";
$result = system(
"runcon -t test_filesystem_no_getattr_t $basedir/mount -s $dev -t $basedir/mntpoint/mp1 -f $fs_type -o $mount_opts $v"
);
ok( $result eq 0 );

$result = system(
"runcon -t test_filesystem_no_getattr_t $basedir/statfs_test -t $basedir/mntpoint $v 2>&1"
);
ok( $result >> 8 eq 13 );

print "Unmount filesystem from $basedir/mntpoint/mp1\n";
$result = system(
"runcon -t test_filesystem_no_getattr_t $basedir/umount -t $basedir/mntpoint/mp1 $v"
);
ok( $result eq 0 );

print "Removing: $dev $basedir/mntpoint $basedir/fstest\n";
cleanup1( $basedir, $dev );

############### Deny filesystem { remount } ##########################
# hooks.c selinux_mount() FILESYSTEM__REMOUNT
mk_mntpoint_1($private_path);

if ($nfs_enabled) {
    $mount_opts = $nfs_mount_opts;
}
else {
    ( $dev, $device_count ) = get_loop_dev( \@device_list, $device_count );
    make_fs( $fs_type, $dev, $basedir );
    $mount_opts =
      "rootcontext=system_u:object_r:test_filesystem_no_remount_t:s0";
}

print "Mount $fs_type filesystem on $basedir/mntpoint/mp1\n";
print "Using mount options:\n\t$mount_opts\n";
$result = system(
"runcon -t test_filesystem_no_remount_t $basedir/mount -s $dev -t $basedir/mntpoint/mp1 -f $fs_type -o $mount_opts $v"
);
ok( $result eq 0 );

print "Then remount\n";
$result = system(
"runcon -t test_filesystem_no_remount_t $basedir/mount -r -s $dev -t $basedir/mntpoint/mp1 -f $fs_type -o $mount_opts $v 2>&1"
);
ok( $result >> 8 eq 13 );

print "Unmount filesystem from $basedir/mntpoint/mp1\n";
$result = system(
"runcon -t test_filesystem_no_remount_t $basedir/umount -t $basedir/mntpoint/mp1 $v"
);
ok( $result eq 0 );

print "Removing: $dev $basedir/mntpoint $basedir/fstest\n";
cleanup1( $basedir, $dev );

############### Deny filesystem { unmount } ##########################
# hooks.c selinux_umount() FILESYSTEM__UNMOUNT
mk_mntpoint_1($private_path);

if ($nfs_enabled) {
    $mount_opts = $nfs_mount_opts;
}
else {
    ( $dev, $device_count ) = get_loop_dev( \@device_list, $device_count );
    make_fs( $fs_type, $dev, $basedir );
    $mount_opts =
      "rootcontext=system_u:object_r:test_filesystem_no_unmount_t:s0";
}

print "Mount $fs_type filesystem on $basedir/mntpoint/mp1\n";
print "Using mount options:\n\t$mount_opts\n";
$result = system(
"runcon -t test_filesystem_no_unmount_t $basedir/mount -s $dev -t $basedir/mntpoint/mp1 -f $fs_type -o $mount_opts $v"
);
ok( $result eq 0 );

print "Unmount filesystem from $basedir/mntpoint/mp1\n";
$result = system(
"runcon -t test_filesystem_no_unmount_t $basedir/umount -t $basedir/mntpoint/mp1 $v 2>&1"
);
ok( $result >> 8 eq 13 );

# Make sure it does get unmounted
print "Unmount filesystem from $basedir/mntpoint/mp1\n";
$result =
  system(
    "runcon -t test_filesystem_t $basedir/umount -t $basedir/mntpoint/mp1 $v");
ok( $result eq 0 );

print "Removing: $dev $basedir/mntpoint $basedir/fstest\n";
cleanup1( $basedir, $dev );

############### Deny filesystem { associate }  ##########################
# hooks.c selinux_inode_setxattr() FILESYSTEM__ASSOCIATE
#
# Tested in nfs_filesystem/test
#
if ( not $nfs_enabled and not $vfat_enabled ) {
    mk_mntpoint_1($private_path);
    ( $dev, $device_count ) = get_loop_dev( \@device_list, $device_count );
    make_fs( $fs_type, $dev, $basedir );
    $mount_opts =
"rootcontext=system_u:object_r:test_filesystem_inode_setxattr_no_associate_t:s0,fscontext=system_u:object_r:test_filesystem_inode_setxattr_no_associate_t:s0";

    print "Mount $fs_type filesystem on $basedir/mntpoint/mp1\n";
    print "Using mount options:\n\t$mount_opts\n";
    $result = system(
"runcon -t test_filesystem_inode_setxattr_no_associate_t $basedir/mount -s $dev -t $basedir/mntpoint/mp1 -f $fs_type -o $mount_opts $v"
    );
    ok( $result eq 0 );

    $result = system(
"runcon -t test_filesystem_inode_setxattr_no_associate_t $basedir/create_file_change_context -t unconfined_t -f $basedir/mntpoint/mp1/test_file $v 2>&1"
    );
    ok( $result >> 8 eq 13 );    # EACCES

    print "Unmount filesystem from $basedir/mntpoint/mp1\n";
    $result = system(
"runcon -t test_filesystem_inode_setxattr_no_associate_t $basedir/umount -t $basedir/mntpoint/mp1 $v"
    );
    ok( $result eq 0 );

    print "Removing: $dev $basedir/mntpoint $basedir/fstest\n";
    cleanup1( $basedir, $dev );
}

if ($test_watch) {
    ############### Deny filesystem { watch }  ##########################
    # hooks.c selinux_path_notify() FILESYSTEM__WATCH
    mk_mntpoint_1($private_path);

    if ($nfs_enabled) {
        $mount_opts = $nfs_mount_opts;
    }
    else {
        ( $dev, $device_count ) = get_loop_dev( \@device_list, $device_count );
        make_fs( $fs_type, $dev, $basedir );
        $mount_opts = "context=system_u:object_r:test_filesystem_no_watch_t:s0";
    }

    print "Mount $fs_type filesystem on $basedir/mntpoint/mp1\n";
    print "Using mount options:\n\t$mount_opts\n";
    $result = system(
"runcon -t test_filesystem_no_watch_t $basedir/mount -s $dev -t $basedir/mntpoint/mp1 -f $fs_type -o $mount_opts $v"
    );
    ok( $result eq 0 );

    print "fanotify(7) test - FAN_MARK_FILESYSTEM\n";
    $result = system(
"runcon -t test_filesystem_no_watch_t $basedir/fanotify_fs $v -t $basedir/mntpoint/mp1 2>&1"
    );
    ok( $result >> 8 eq 13 );

    print "Unmount filesystem from $basedir/mntpoint/mp1\n";
    $result = system(
"runcon -t test_filesystem_no_watch_t $basedir/umount -t $basedir/mntpoint/mp1 $v"
    );
    ok( $result eq 0 );

    print "Removing: $dev $basedir/mntpoint $basedir/fstest\n";
    cleanup1( $basedir, $dev );

    ############### Deny file { watch_sb }  ##########################
    # hooks.c selinux_path_notify() FILE__WATCH_SB
    mk_mntpoint_1($private_path);

    if ($nfs_enabled) {
        $mount_opts = $nfs_mount_opts;
    }
    else {
        ( $dev, $device_count ) = get_loop_dev( \@device_list, $device_count );
        make_fs( $fs_type, $dev, $basedir );
        $mount_opts =
          "context=system_u:object_r:test_filesystem_no_watch_sb_t:s0";
    }

    print "Mount $fs_type filesystem on $basedir/mntpoint/mp1\n";
    print "Using mount options:\n\t$mount_opts\n";
    $result = system(
"runcon -t test_filesystem_no_watch_sb_t $basedir/mount -s $dev -t $basedir/mntpoint/mp1 -f $fs_type -o $mount_opts $v"
    );
    ok( $result eq 0 );

    print "fanotify(7) test - FAN_MARK_FILESYSTEM\n";
    $result = system(
"runcon -t test_filesystem_no_watch_sb_t $basedir/fanotify_fs $v -t $basedir/mntpoint/mp1 2>&1"
    );
    ok( $result >> 8 eq 13 );

    print "Unmount filesystem from $basedir/mntpoint/mp1\n";
    $result = system(
"runcon -t test_filesystem_no_watch_sb_t $basedir/umount -t $basedir/mntpoint/mp1 $v"
    );
    ok( $result eq 0 );

    print "Removing: $dev $basedir/mntpoint $basedir/fstest\n";
    cleanup1( $basedir, $dev );

    ############### Deny file { watch_mount }  ##########################
    # hooks.c selinux_path_notify() FILE__WATCH_MOUNT
    mk_mntpoint_1($private_path);

    if ($nfs_enabled) {
        $mount_opts = $nfs_mount_opts;
    }
    else {
        ( $dev, $device_count ) = get_loop_dev( \@device_list, $device_count );
        make_fs( $fs_type, $dev, $basedir );
        $mount_opts =
          "context=system_u:object_r:test_filesystem_no_watch_mount_t:s0";
    }

    print "Mount $fs_type filesystem on $basedir/mntpoint/mp1\n";
    print "Using mount options:\n\t$mount_opts\n";
    $result = system(
"runcon -t test_filesystem_no_watch_mount_t $basedir/mount -s $dev -t $basedir/mntpoint/mp1 -f $fs_type -o $mount_opts $v"
    );
    ok( $result eq 0 );

    print "fanotify(7) test - FAN_MARK_MOUNT\n";
    $result = system(
"runcon -t test_filesystem_no_watch_mount_t $basedir/fanotify_fs $v -m -t $basedir/mntpoint/mp1 2>&1"
    );
    ok( $result >> 8 eq 13 );

    print "Unmount filesystem from $basedir/mntpoint/mp1\n";
    $result = system(
"runcon -t test_filesystem_no_watch_mount_t $basedir/umount -t $basedir/mntpoint/mp1 $v"
    );
    ok( $result eq 0 );

    print "Removing: $dev $basedir/mntpoint $basedir/fstest\n";
    cleanup1( $basedir, $dev );
}

############### Test Invalid Mount ##########################
# This will generate a log entry "SELinux: mount invalid. Same superblock,
#    different security settings for (dev 0:49, type nfs4)"
# Note that NFS is already mounted at this point by nfs.sh
#
mk_mntpoint_1($private_path);

if ($nfs_enabled) {
    $mount_inval_opts = $nfs_inval_mount_opts;
}
else {
    ( $dev, $device_count ) = get_loop_dev( \@device_list, $device_count );
    make_fs( $fs_type, $dev, $basedir );
    $mount_opts = "rootcontext=system_u:object_r:test_filesystem_file_t:s0";
    $mount_inval_opts = "fscontext=system_u:object_r:test_filesystem_file_t:s0";

    print "Mount $fs_type filesystem on $basedir/mntpoint/mp1\n";
    print "Using mount options:\n\t$mount_opts\n";
    $result = system(
"runcon -t test_filesystem_t $basedir/mount -s $dev -t $private_path/mp1 -f $fs_type -o $mount_opts $v"
    );
    ok( $result eq 0 );
}

print "Test 'Invalid Mount' $fs_type filesystem on $private_path/mp1\n";
print "Mount $fs_type filesystem on $private_path/mp1\n";
print "Using mount options:\n\t$mount_inval_opts\n";
$result = system(
"runcon -t test_filesystem_t $basedir/mount -s $dev -t $private_path/mp1 -f $fs_type -o $mount_inval_opts $v 2>&1"
);
if ( $nfs_enabled and $result >> 8 eq 16 ) {
    ok( 1, "Returned EBUSY, known bug" );
}
else {
    system(
"runcon -t test_filesystem_t $basedir/umount -t $private_path/mp1 $v 2>&1"
    );
    ok( $result >> 8 eq 22 );    # EINVAL
}

print "Removing: $basedir/mntpoint\n";
cleanup1( $basedir, $dev );

if ( not $nfs_enabled ) {
    ##########################################################################
    # context     - Useful when mounting filesystems that do not support
    #               extended attributes.
    #               Note when testing vfat the test will fail earlier, but
    #               just carry on
    #   Tested by - Creating a filesystem that has xattrs set to a different
    #               value, then mount with context= and confirm that the files
    #               have that context as well as any newly created files (even
    #               if fscreate was set to something else), and that
    #               setfilecon/setxattr() on files within the mount fails with
    #               errno EOPNOTSUPP.
    ##########################################################################
    mk_mntpoint_1($private_path);
    ( $dev, $device_count ) = get_loop_dev( \@device_list, $device_count );
    make_fs( $fs_type, $dev, $basedir );

    # Mount with xttrs to create a file with specific context.
    $context1_opts =
      "rootcontext=system_u:object_r:test_filesystem_context_file_t:s0";

    print "Testing 'context=' mount option\n";
    print "Mount $fs_type filesystem on $basedir/mntpoint/mp1\n";
    print "Using mount options:\n\t$context1_opts\n";
    $result = system(
"runcon -t test_filesystem_context_t $basedir/mount -s $dev -t $basedir/mntpoint/mp1 -f $fs_type -o $context1_opts $v"
    );
    ok( $result eq 0 );

    # Create file with 'test_filesystem_filecon_t' context
    if ( not $vfat_enabled ) {
        print "Creating test file $basedir/mntpoint/mp1/test_file\n";
        $result =
          system(
"runcon -t test_filesystem_context_t $basedir/create_file_change_context -t test_filesystem_filecon_t -f $private_path/mp1/test_file $v"
          );
        ok( $result eq 0 );
    }
    else {
        system("touch $private_path/mp1/test_file");
    }

    print "Unmount filesystem from $basedir/mntpoint/mp1\n";
    $result = system(
"runcon -t test_filesystem_context_t $basedir/umount -t $basedir/mntpoint/mp1 $v"
    );
    ok( $result eq 0 );

    # Need to free the loop device, then get new one and attach
    system("losetup -d $dev 2>/dev/null");
    ( $dev, $device_count ) = get_loop_dev( \@device_list, $device_count );
    attach_dev( $dev, $basedir );

    # Mount again with no xttr support
    $context2_opts =
      "context=system_u:object_r:test_filesystem_context_file_t:s0";
    print "Mount $fs_type filesystem on $basedir/mntpoint/mp1\n";
    print "Using mount options:\n\t$context2_opts\n";
    $result = system(
"runcon -t test_filesystem_context_t $basedir/mount -s $dev -t $basedir/mntpoint/mp1 -f $fs_type -o $context2_opts $v"
    );
    ok( $result eq 0 );

# Now check the context on file is system_u:object_r:test_filesystem_context_file_t:s0
    print "Check test file context $basedir/mntpoint/mp1/test_file\n";
    $result =
      system(
"runcon -t test_filesystem_context_t $basedir/check_file_context -f $private_path/mp1/test_file -e system_u:object_r:test_filesystem_context_file_t:s0 $v"
      );
    ok( $result eq 0 );

# Then create a file with 'test_filesystem_filecon_t' context, this should fail with EOPNOTSUPP
    print "Creating test file $basedir/mntpoint/mp1/test_file\n";
    $result =
      system(
"runcon -t test_filesystem_context_t $basedir/create_file_change_context -t test_filesystem_filecon_t -f $private_path/mp1/test_file $v 2>/dev/null"
      );
    ok( $result >> 8 eq 95 );

    print "Unmount filesystem from $basedir/mntpoint/mp1\n";
    $result =
      system(
"runcon -t test_filesystem_context_t $basedir/umount -t $basedir/mntpoint/mp1 $v"
      );
    ok( $result eq 0 );

    print "Removing: $dev $basedir/mntpoint $basedir/fstest\n";
    cleanup1( $basedir, $dev );

    ##########################################################################
    # rootcontext - Explicitly label the root inode of the filesystem being
    #               mounted before that filesystem or inode becomes visible
    #               to userspace.
    #   Tested by - Set mountpoint to unlabeled_t and then check that the
    #               context of the root directory matches rootcontext= after
    #               the mount operation.
    ##########################################################################
    mk_mntpoint_1($private_path);
    ( $dev, $device_count ) = get_loop_dev( \@device_list, $device_count );
    make_fs( $fs_type, $dev, $basedir );
    $root_opts =
      "rootcontext=system_u:object_r:test_filesystem_context_file_t:s0";

    print "Testing 'rootcontext=' mount option\n";

# Reset mountpoint to 'unlabeled_t' so it is different to any other possible test values.
    print "Resetting MP to unlabeled_t $basedir/mntpoint/mp1\n";
    $result =
      system(
"runcon -t test_filesystem_context_t $basedir/check_mount_context -r -m $basedir/mntpoint/mp1 $v"
      );
    ok( $result eq 0 );

    print "Mount $fs_type filesystem on $basedir/mntpoint/mp1\n";
    print "Using mount options:\n\t$root_opts\n";
    $result = system(
"runcon -t test_filesystem_context_t $basedir/mount -s $dev -t $basedir/mntpoint/mp1 -f $fs_type -o $root_opts $v"
    );
    ok( $result eq 0 );

    # Now check the mountpoint is the 'rootcontext=' value
    print "Check MP context $basedir/mntpoint/mp1\n";
    $result =
      system(
"runcon -t test_filesystem_context_t $basedir/check_mount_context -m $basedir/mntpoint/mp1 -e system_u:object_r:test_filesystem_context_file_t:s0 $v"
      );
    ok( $result eq 0 );

    print "Unmount filesystem from $basedir/mntpoint/mp1\n";
    $result = system(
"runcon -t test_filesystem_context_t $basedir/umount -t $basedir/mntpoint/mp1 $v"
    );
    ok( $result eq 0 );

    print "Removing: $dev $basedir/mntpoint $basedir/fstest\n";
    cleanup1( $basedir, $dev );

    if ( not $vfat_enabled ) {
        #######################################################################
      # defcontext  - Set default security context for unlabeled files.
      #               This overrides the value set for unlabeled files in policy
      #               and requires a filesystem that supports xattr labeling.
      #   Tested by - Create filesystem that has files w/o xattrs and then
      #               confirm that they are mapped to the specified defcontext
      #               upon mount, where defcontext differs from the policy
      #               default.
        #######################################################################
        mk_mntpoint_1($private_path);
        ( $dev, $device_count ) = get_loop_dev( \@device_list, $device_count );
        make_fs( $fs_type, $dev, $basedir );
        $test_opts =
          "context=system_u:object_r:test_filesystem_context_file_t:s0";

        print "Testing 'defcontext=' mount option\n";
        print "Mount $fs_type filesystem on $basedir/mntpoint/mp1\n";
        print "Using mount options:\n\t$test_opts\n";
        $result = system(
"runcon -t test_filesystem_context_t $basedir/mount -s $dev -t $basedir/mntpoint/mp1 -f $fs_type -o $test_opts $v"
        );
        ok( $result eq 0 );

        # Create file, its context will be:
        #   system_u:object_r:test_filesystem_context_file_t:s0 from $test_opts
        print "Creating test file $basedir/mntpoint/mp1/test_file\n";
        $result = system(
"runcon -t test_filesystem_fscontext_t $basedir/create_file -f $basedir/mntpoint/mp1/test_file -e test_filesystem_context_file_t $v"
        );
        ok( $result eq 0 );

        print "Unmount filesystem from $basedir/mntpoint/mp1\n";
        $result = system(
"runcon -t test_filesystem_context_t $basedir/umount -t $basedir/mntpoint/mp1 $v"
        );
        ok( $result eq 0 );

        # Need to free the loop device, then get new dev one and attach
        system("losetup -d $dev 2>/dev/null");
        ( $dev, $device_count ) = get_loop_dev( \@device_list, $device_count );
        attach_dev( $dev, $basedir );

        # Mount again with defcontext=
        $defcontext_opts =
          "defcontext=system_u:object_r:test_filesystem_filecon_t:s0";
        print "Mount $fs_type filesystem on $basedir/mntpoint/mp1\n";
        print "Using mount options:\n\t$defcontext_opts\n";
        $result = system(
"runcon -t test_filesystem_context_t $basedir/mount -s $dev -t $basedir/mntpoint/mp1 -f $fs_type -o $defcontext_opts $v"
        );
        ok( $result eq 0 );

# Now check the file context is now system_u:object_r:test_filesystem_filecon_t:s0
        print "Check test file context $basedir/mntpoint/mp1/test_file\n";
        $result = system(
"runcon -t test_filesystem_context_t $basedir/check_file_context -f $basedir/mntpoint/mp1/test_file -e system_u:object_r:test_filesystem_filecon_t:s0 $v"
        );
        ok( $result eq 0 );

        print "Unmount filesystem from $basedir/mntpoint/mp1\n";
        $result = system(
"runcon -t test_filesystem_context_t $basedir/umount -t $basedir/mntpoint/mp1 $v"
        );
        ok( $result eq 0 );

        print "Removing: $dev $basedir/mntpoint $basedir/fstest\n";
        cleanup1( $basedir, $dev );
    }

    ##########################################################################
    # fscontext   - Sets the overarching filesystem label to a specific
    #               security context. This filesystem label is separate from
    #               the individual labels on the files.
    #   Tested by - Mount a tmpfs (fs_use_trans) filesystem with fscontext=
    #               and then create a file within it, checking its context.
    ##########################################################################
    $fs_type = "tmpfs";
    mk_mntpoint_1($private_path);
    ( $dev, $device_count ) = get_loop_dev( \@device_list, $device_count );
    $fscontext_opts =
"fscontext=system_u:object_r:test_filesystem_fscontext_fs_t:s0,size=10M,mode=0770";

    print "Testing 'fscontext=' mount option\n";
    print "Mount tmpfs filesystem on $basedir/mntpoint/mp1\n";
    print "Using mount options:\n\t$fscontext_opts\n";
    $result = system(
"runcon -t test_filesystem_fscontext_t $basedir/mount -s $dev -t $basedir/mntpoint/mp1 -f $fs_type -o $fscontext_opts $v"
    );
    ok( $result eq 0 );

    print "Creating test file $basedir/mntpoint/mp1/test_file\n";
    $result =
      system(
"runcon -t test_filesystem_fscontext_t $basedir/create_file_change_context -t test_filesystem_filecon_t -f $private_path/mp1/test_file $v"
      );
    ok( $result eq 0 );

    print "Unmount filesystem from $basedir/mntpoint/mp1\n";
    $result =
      system(
"runcon -t test_filesystem_fscontext_t $basedir/umount -t $basedir/mntpoint/mp1 $v"
      );
    ok( $result eq 0 );

    print "Removing: $dev $basedir/mntpoint $basedir/fstest\n";
    cleanup1( $basedir, $dev );
}

reaper( \@device_list, $basedir, $v );

if ($disable_udisks) {
    udisks2_restart($udisks2_status);
}

exit;
